package com.cee.admin.auth.controller;

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.cee.admin.common.BaseController;
import com.cee.admin.common.utils.HttpServletUtils;
import com.cee.admin.common.utils.SysUserUtils;
import com.cee.admin.config.shiro.jwt.TokenService;
import com.cee.admin.log.annotations.OperateLog;
import com.cee.cache.redis.RedisUtils;
import com.cee.common.exception.BaseException;
import com.cee.common.exception.GlobalException;
import com.cee.common.web.result.Result;
import com.cee.domain.modules.ece.entity.IndustrialParks;
import com.cee.domain.modules.entity.ImageCode;
import com.cee.domain.modules.sys.entity.SysUserLoginLog;
import com.cee.domain.modules.sys.entity.User;
import com.cee.domain.modules.sys.vo.UserVo;
import com.cee.ds.common.utils.BasePrincipalUtils;
import com.cee.ds.common.utils.PasswordUtils;
import com.cee.ds.modules.auth.AuthService;
import com.cee.ds.modules.ece.service.IIndustrialParksService;
import com.cee.ds.modules.sys.service.ISysUserLoginLogService;
import com.cee.ds.modules.sys.service.IUserService;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.Date;
import java.util.List;

import static com.cee.common.web.result.ResultCode.*;


@RestController
@RequestMapping("/auth")
@Tag(name = "验证模块")
public class AuthController extends BaseController {

    private static final String JS_IMAGE_DATA_PREFIX = "data:image/png;base64,";
    /**
     * 管理端登录校验次数的key的前缀
     */
    private static final String REDIS_KEY_ADMIN_LOGIN_VERIFY_NUM = "c:admin:login:verifyNum:";
    /**
     * 管理端登录校验禁止登录标识的key的前缀
     */
    private static final String REDIS_KEY_ADMIN_LOGIN_BAN_TIME = "c:admin:login:banTime:";

    /**
     * 最大错误次数
     */
    private static final Integer MAX_ERR_NUM = 5;
    /**
     * 锁定时间
     */
    private static final Integer LOGIN_BAN_TIME = 5 * 60;

    @Autowired
    private IUserService iUserService;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private AuthService authService;

    @Autowired
    private DefaultKaptcha defaultKaptcha;

    @Autowired
    private ISysUserLoginLogService loginLogService;

    @Autowired
    private IIndustrialParksService industrialParksService;

    @Autowired
    private RedisUtils redisUtils;

    /**
     * 登录
     *
     * @param imageKey
     * @param userVo
     * @return
     */
    @PostMapping("login")
    @Operation(summary = "登录")
    @OperateLog(enable = false)
    public Result<String> login(@RequestHeader("${validate.image-code.image-head}") String imageKey, @RequestBody UserVo userVo) {
        String username = userVo.getLoginName();

        if (StringUtils.isEmpty(username)) {
            throw new GlobalException("账号密码错误，请重新登录!");
        }
        // 登录前校验是否存在禁止登录标志，若存在直接返回
        Long redisUtilsExpire = redisUtils.getMinExpire(REDIS_KEY_ADMIN_LOGIN_BAN_TIME + username);
        if (redisUtilsExpire > 0) {
            return error("您的账号密码错误次数已超过上限，账号已被锁定，请在" + redisUtilsExpire + "分钟后重试");
        }
        String token;
        Date loginTime = new Date();
        try {
            //验证验证码
            authService.validateCode(userVo.getImageCode(), imageKey);
            // 处理用户Vo
            User user = handleUserInLogin(userVo);
            //生成token
            token = tokenService.generateToken(user);
        } catch (Throwable e) {
            String msg = null;
            if (e instanceof BaseException) {
                msg = e.getMessage();
            }
            recordLoginLog(username, loginTime, "0", msg);
            //若存在则加一，加一后大于5次锁定，并清除标记，不存在则创建标记,初始值为1
            Long verifyNum = redisUtils.get(REDIS_KEY_ADMIN_LOGIN_VERIFY_NUM + username, Long.class);
            if (verifyNum == null) {
                redisUtils.set(REDIS_KEY_ADMIN_LOGIN_VERIFY_NUM + username, 1L);
            } else if (verifyNum >= MAX_ERR_NUM) {
                redisUtils.del(REDIS_KEY_ADMIN_LOGIN_VERIFY_NUM + username);
                redisUtils.set(REDIS_KEY_ADMIN_LOGIN_BAN_TIME + username, null, LOGIN_BAN_TIME);
            } else {
                redisUtils.incr(REDIS_KEY_ADMIN_LOGIN_VERIFY_NUM + username, 1);
            }
            throw e;
        }
        recordLoginLog(userVo.getLoginName(), loginTime, "1", "登录成功");
        // 登录成功则重新计数
        redisUtils.del(REDIS_KEY_ADMIN_LOGIN_VERIFY_NUM + username);
        return success(token);
    }

    /**
     * 处理登录时用户对象
     *
     * @param userVo 用户Vo
     * @return 是否继续
     */
    public User handleUserInLogin(UserVo userVo) {
        //验证账号密码
        String username = userVo.getLoginName();
        if (StringUtils.isEmpty(username)) {
            throw new GlobalException("账号密码错误，请重新登录!");
        }

        User user = iUserService.getUserInfoByUserName(username);

        if (ObjectUtils.isEmpty(user)) {
            throw new GlobalException("账号密码错误，请重新登录!");
        }

        if (BasePrincipalUtils.isDisable(user.getStatus())) {
            throw new GlobalException(USER_ACCOUNT_DISABLE);
        }

        //对比密码
        if (!StringUtils.equals(PasswordUtils.encryptedPassword(userVo.getPassword(), user.getSalt()), user.getPassword())) {
            throw new GlobalException("账号密码错误，请重新登录!");
        }
        return user;
    }

    /**
     * 直接登录，无需验证
     *
     * @param userVo 用户信息
     * @return token字符串
     */
    @PostMapping("login/novalid")
    @Operation(summary = "无验证码登录")
    @OperateLog(enable = false)
    public Result<String> loginNoValid(@RequestBody UserVo userVo) {
        Date loginTime = new Date();
        String token = null;
        try {

            // 处理用户Vo
            User user = handleUserInLogin(userVo);
            //生成token
            token = tokenService.generateToken(user);
        } catch (Throwable e) {
            String msg = null;
            if (e instanceof BaseException) {
                msg = e.getMessage();
            }
            recordLoginLog(userVo.getLoginName(), loginTime, "0", msg);
            throw e;
        }
        recordLoginLog(userVo.getLoginName(), loginTime, "1", "登录成功");
        return success(token);
    }

    /**
     * 刷新token
     *
     * @param token 旧的凭证
     * @return 新的凭证
     */
    @PostMapping("refresh-token")
    @Operation(summary = "刷新token")
    public Result<String> refreshToken(@RequestHeader("${jwt.header}") String token) {
        //刷新token
        String newToken = tokenService.refreshToken(token);
        return success(newToken);
    }

    /**
     * 验证码
     *
     * @return
     * @throws Exception
     */
    @GetMapping("/code/image")
    @Operation(summary = "获取验证码")
    public Result<ImageCode> createCode() throws IOException {
        //图片转成数组 进行base64编码 通过字符串返回。
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            // 将生成的验证码保存在redis中
            String createText = defaultKaptcha.createText();
            final String imageKey = IdWorker.getIdStr();
            authService.setImageCode(imageKey, createText);
            BufferedImage bi = defaultKaptcha.createImage(createText);
            ImageIO.write(bi, "png", out);
            final byte[] bytes = out.toByteArray();
            Base64.Encoder encoder = Base64.getEncoder();
            final String encode = new String(encoder.encode(bytes));
            return success(new ImageCode().setImageKey(imageKey).setImageData(JS_IMAGE_DATA_PREFIX + encode));
        }
    }

    /**
     * 登出
     *
     * @param token
     * @return
     */
    @PostMapping("logout")
    @Operation(summary = "登出")
    @OperateLog(enable = false)
    public Result<Boolean> logout(@RequestHeader("${jwt.header}") String token) {
        String loginName = SysUserUtils.currentUser().getLoginName();
        Date startTime = new Date();
        try {
            // shiro登出
            SecurityUtils.getSubject().logout();
            tokenService.removeToken(token);
            recordLoginLog(loginName, startTime, "1", "退出成功");
        } catch (Exception e) {
            String msg = null;
            if (e instanceof BaseException) {
                msg = e.getMessage();
            }
            recordLoginLog(loginName, startTime, "0", "退出失败，" + msg);
            throw e;
        }
        return success(true);
    }

    @Operation(summary = "根据地址查询对应园区列表")
    @GetMapping("/industrialParks/listByAddress")
    public Result<List<IndustrialParks>> ListByAddress(@RequestParam Long provinceId,
                                                       @RequestParam Long cityId,
                                                       @RequestParam Long areaId){
        return success(industrialParksService.listByAddress(provinceId, cityId, areaId));
    }

    @Transactional
    public void recordLoginLog(String loginName, Date loginTime, String status, String msg) {
        HttpServletRequest request = HttpServletUtils.getRequest();
        SysUserLoginLog loginLog = new SysUserLoginLog();
        loginLog.setLoginName(loginName);
        loginLog.setStartTime(loginTime);
        loginLog.setStatus(status);
        loginLog.setMsg(msg);
        loginLog.setIpAddress(HttpServletUtils.getClientIP(request));
        loginLog.setUserAgent(HttpServletUtils.getUserAgent(request));
        loginLogService.asyncSave(loginLog);
        // 登录之后还需要更新当前登录用户的最后登录时间
        iUserService.updateLastLoginTime(loginName, loginTime);
    }
}
